﻿using System;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Parsing;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Impl.Types;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;

namespace MugenMvvmToolkit.ReSharper
{
    public static class TypeExtensions
    {
        #region Methods

        public static bool HasAttributeInstance([NotNull] this ITypeMemberDeclaration typeMemberDeclaration,
            [NotNull] IDeclaredType attribute, bool inherit = true)
        {
            if (typeMemberDeclaration == null) throw new ArgumentNullException("typeMemberDeclaration");
            if (attribute == null) throw new ArgumentNullException("attribute");
            return typeMemberDeclaration.DeclaredElement != null &&
                   typeMemberDeclaration.DeclaredElement.HasAttributeInstance(attribute.GetClrName(), inherit);
        }

        public static bool IsOfType([NotNull] this IMemberOwnerDeclaration memberOwnerDeclaration, [NotNull] IType typeTo)
        {
            if (memberOwnerDeclaration == null) throw new ArgumentNullException("memberOwnerDeclaration");
            if (typeTo == null) throw new ArgumentNullException("typeTo");
            ITypeElement declaredElement = memberOwnerDeclaration.DeclaredElement;
            if (declaredElement == null)
                return false;
            return declaredElement.IsOfType(typeTo);
        }

        public static bool IsOfType([NotNull] this IAttribute attribute, [NotNull] IType typeTo)
        {
            if (attribute == null) throw new ArgumentNullException("attribute");
            if (typeTo == null) throw new ArgumentNullException("typeTo");
            var @class = attribute.TypeReference.GetDeclaredElement<IClass>();
            return @class != null && @class.IsOfType(typeTo);
        }

        public static bool IsOfType([NotNull] this ITypeElement typeElementFrom, [NotNull] IType typeTo)
        {
            if (typeElementFrom == null) throw new ArgumentNullException("typeElementFrom");
            if (typeTo == null) throw new ArgumentNullException("typeTo");
            if (!TryResolve(ref typeTo))
                return false;
            var typeElementTo = typeTo.GetTypeElement<ITypeElement>();
            if (typeElementTo == null)
                return TypeFactory.CreateType(typeElementFrom).IsOfType(typeTo);
            return Equals(typeElementFrom, typeElementTo) || typeElementFrom.IsDescendantOf(typeElementTo);
        }

        public static bool IsOfType([NotNull] this IType typeFrom, [NotNull] IType typeTo)
        {
            if (typeFrom == null) throw new ArgumentNullException("typeFrom");
            if (typeTo == null) throw new ArgumentNullException("typeTo");
            if (!TryResolve(ref typeFrom) || !TryResolve(ref typeTo))
                return false;
            if (Equals(typeFrom, typeTo))
                return true;

            var elementFrom = typeFrom.GetTypeElement<ITypeElement>();
            var elementTo = typeTo.GetTypeElement<ITypeElement>();
            if (elementFrom != null && elementTo != null)
                return Equals(elementFrom, elementTo) || elementFrom.IsDescendantOf(elementTo);
            return Equals(typeFrom, typeTo) ||
                   typeFrom.IsExplicitlyConvertibleTo(typeTo, ClrPredefinedTypeConversionRule.INSTANCE)
                   || typeFrom.IsImplicitlyConvertibleTo(typeTo, ClrPredefinedTypeConversionRule.INSTANCE);
        }

        public static bool IsStaticEx([NotNull] this IClassMemberDeclaration classMemberDeclaration)
        {
            if (classMemberDeclaration == null) throw new ArgumentNullException("classMemberDeclaration");
            return classMemberDeclaration.ModifiersList.HasModifier(CSharpTokenType.STATIC_KEYWORD);
        }

        public static bool HasBaseClass([NotNull] this IClassDeclaration classDeclaration)
        {
            if (classDeclaration == null) throw new ArgumentNullException("classDeclaration");
            ITypeElement declaredElement = classDeclaration.DeclaredElement;
            if (declaredElement == null)
                return false;
            IDeclaredType baseClass = declaredElement.GetSuperTypes().SingleOrDefault(type => type.IsClassType());
            return baseClass != null && !baseClass.IsObject();
        }

        public static bool IsGenericClass([NotNull] this IDeclaredType type)
        {
            if (type == null) throw new ArgumentNullException("type");
            ITypeElement typeElement = type.GetTypeElement();
            return typeElement != null && typeElement.TypeParameters.Count != 0;
        }

        public static bool TryResolve<T>(ref T type) where T : class, IType
        {
            if (type.IsResolved)
                return true;
            var declaredType = type as IDeclaredType;
            if (declaredType == null)
                return false;
            IResolveResult resolveResult = declaredType.Resolve();
            var typeElement = resolveResult.DeclaredElement as ITypeElement;
            if (typeElement == null)
                return false;
            type = new DeclaredTypeFromCLRName(typeElement.GetClrName(), type.Module, type.GetResolveContext()) as T;
            return type != null && type.IsResolved;
        }

        public static bool IsReturnTypeTask([CanBeNull]this IParametersOwner parameters, TypeProvider typeProvider)
        {
            return parameters != null && parameters.ReturnType.IsOfType(typeProvider.Task);
        }

        #endregion
    }
}